package controller

import (
	"errors"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
	"github.com/electricbubble/gadb"
	"go-scrcpy/internal/codec"
	"go-scrcpy/internal/sink"
	"go-scrcpy/internal/sink/fy"
	"go-scrcpy/internal/source"
	"go-scrcpy/internal/source/tcp"
	"go-scrcpy/internal/ui"
	"go-scrcpy/pkg/adb"
	"go-scrcpy/pkg/event"
	"go-scrcpy/pkg/log"
	"go-scrcpy/pkg/util"
	"os"
)

const (
	deviceServerPath = "/data/local/tmp/scrcpy-Device.jar"
	sockName         = "scrcpy"
	defaultMainClass = "com.genymobile.scrcpy.Server"
)

type DeviceController struct {
	adbCli          gadb.Client
	deviceStatusMap map[string]int8
	textureMap      map[source.Source]*ui.TextureCard
	gridLayout      *fyne.Container
	win             fyne.Window
}

func NewDeviceController(adbCli gadb.Client) *DeviceController {
	return &DeviceController{
		adbCli:          adbCli,
		deviceStatusMap: make(map[string]int8),
		textureMap:      make(map[source.Source]*ui.TextureCard),
	}
}

func (s *DeviceController) OnNewSource(ss source.Source) {
	log.Info("onNewSource")
	NewTextureCard := ui.NewTextureCard(ss.Format().Size.Width/3, ss.Format().Size.Height/3, ss)
	videoSink := fy.NewCanvasImageSink(NewTextureCard.Pix(), NewTextureCard.Image, sink.Width(ss.Format().Size.Width/3), sink.Height(ss.Format().Size.Height/3))
	f, _, _ := codec.NewFrameFactory()
	s.textureMap[ss] = NewTextureCard
	decoder := codec.NewDecoder(f, ss, videoSink)
	s.gridLayout.Add(NewTextureCard)
	s.gridLayout.Refresh()
	decoder.Start()
}

func (s *DeviceController) Start(win fyne.Window) error {
	s.win = win
	s.win.SetContent(s.layout())
	event.Bus().SubscribeAsync(event.DecoderStop, s.handleEvent, false)
	return nil
}

func (s *DeviceController) handleEvent(decoder *codec.Decoder, ss source.Source, videoSink sink.Sink) {
	log.Info("handleEvent")
	delete(s.deviceStatusMap, ss.Name())
	if u, ok := s.textureMap[ss]; ok {
		s.gridLayout.Remove(u)
		s.refresh()
		u.Stop()
	}
	log.Info("on closed ss = %s", ss.Name())
	delete(s.textureMap, ss)
	decoder.Stop()
	ss.Close()
	videoSink.Close()
}

func (s *DeviceController) Stop(win fyne.Window) error {
	event.Bus().Unsubscribe(event.DecoderStop, s.handleEvent)
	return nil
}

func (s *DeviceController) refresh() {
	s.List()
}

func (s *DeviceController) layout() *fyne.Container {
	t := widget.NewToolbar(
		widget.NewToolbarAction(theme.ContentRedoIcon(), func() {
			s.refresh()
		}),
		widget.NewToolbarSeparator(),
		widget.NewToolbarAction(theme.ContentAddIcon(), func() {
			w := fyne.CurrentApp().NewWindow("Central")
			w.Resize(fyne.NewSize(640, 320))
			s.ShowSettingDialog(w)
			w.CenterOnScreen()
			w.Show()
		}))
	s.List()

	s.gridLayout = container.NewGridWrap(fyne.NewSize(360, 640))
	mainLayout := container.NewVBox(t, s.gridLayout)
	return mainLayout
}

func (s *DeviceController) List() (res []string, err error) {
	res, err = s.adbCli.DeviceSerialList()
	if err != nil {
		log.Error("DeviceSerialList err = %v", err)
		return
	}
	path := fyne.CurrentApp().Preferences().StringWithFallback("server_jar", "")
	if path == "" {
		log.Error("StringWithFallback err = %v", err)
		dialog.ShowError(errors.New("cannot found scrcpy-server.jar"), s.win)
		return
	}
	f, err := os.Open(path)
	if err != nil {
		log.Error("open file fail,err %v", err)
		dialog.ShowError(err, s.win)
		return
	}
	defer f.Close()
	devices, err := s.adbCli.DeviceList()
	if err != nil {
		log.Error("DeviceList err = %v", err)
		return
	}
	for _, d := range devices {
		if _, ok := s.deviceStatusMap[d.Serial()]; ok {
			log.Info("device map has %s", d.Serial())
			continue
		}

		log.Info("tcp = %s", d.Serial())
		if err = d.PushFile(f, deviceServerPath); err != nil {
			log.Error("push file fail %v", err)
			continue
		}
		port, _ := util.GetFreePort()
		log.Info("get free port  = %d", port)
		ss, err := tcp.NewSourceClient(source.LocalPort(port),
			source.Serial(d.Serial()),
			source.SockName(sockName),
			source.ServerPath(deviceServerPath))
		if err != nil {
			log.Error("new source fail ,err = %v", err)
			continue
		}
		_ = adb.RemovePath(d.Serial(), deviceServerPath)
		s.deviceStatusMap[d.Serial()] = 1
		go s.OnNewSource(ss)
	}
	log.Info("loop again")
	return
}

func (s *DeviceController) ShowSettingDialog(win fyne.Window) {
	name := widget.NewEntry()
	name.SetPlaceHolder("d:/program/adb.exe")
	if v := fyne.CurrentApp().Preferences().StringWithFallback("adb_path", "null"); v != "null" {
		name.SetText(v)
	}
	email := widget.NewEntry()
	email.SetPlaceHolder("d:/program/server.jar")
	if v := fyne.CurrentApp().Preferences().StringWithFallback("server_jar", "null"); v != "null" {
		email.SetText(v)
	}
	form := &widget.Form{
		Items: []*widget.FormItem{
			{Text: "adb path", Widget: name, HintText: "adb path"},
			{Text: "jar path", Widget: email, HintText: "jar path"},
		},
		OnCancel: func() {
			win.Close()
		},
		OnSubmit: func() {
			fyne.CurrentApp().Preferences().SetString("adb_path", name.Text)
			fyne.CurrentApp().Preferences().SetString("server_jar", email.Text)
			win.Close()
		},
	}
	win.SetContent(form)
}
